/*-------------------------------------------------------------------------
 *
 * aset.c
 *	  分配集定义。
 *
 * AllocSet 是我们的标准实现的抽象 MemoryContext 类型。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/utils/mmgr/aset.c
 *
 * NOTE:
 *	这是一个新的（1999 年 2 月 5 日）分配集例程的实现。AllocSet...() 不再使用
 * OrderedSet...()。相反，它自行管理块池中的分配，将许多小的分配组合到几个更大的块中。
 * AllocSetFree() 通常不会真正 free() 内存。它只是将释放的区域添加到某个列表中，以便稍后由 AllocSetAlloc() 重用。
 * 在 AllocSetReset() 时，所有内存块会被 free()，这发生在内存上下文被销毁时。
 *				Jan Wieck
 *
 *	性能改进来自 Tom Lane, 99 年 8 月：对于极大请求大小，我们希望能够在调用
 * pfree() 时立即将内存归还给 free()。否则，我们有可能将大量内存困在可能永远无法使用的空闲列表条目中。
 * 当调用者反复进行 realloc() 以增大块时，之前的块实例在旧方式下保证浪费。
 *
 *	进一步改进 00 年 12 月：按原样写的代码，在“小”与“大”之间的中等请求大小
 * 的处理效率非常低，因为任何足够大的空闲块都将用于满足请求，即使它远大于所需。这导致随着时间的推移，分配块中浪费的空间越来越多。为了解决这个问题，去掉中等行为：我们现在只处理“大小”的“2 的幂”块。任何“大的”都转给 malloc()。更改
 * 空闲列表的数量以更改小/大边界。
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "port/pg_bitutils.h"
#include "utils/memdebug.h"
#include "utils/memutils.h"

/*--------------------
 * Chunk freelist k 持有大小为 1 << (k + ALLOC_MINBITS) 的块，
 * 对于 k = 0 .. ALLOCSET_NUM_FREELISTS-1。
 *
 * 请注意，自由列表中的所有块都有 2 的幂的大小。这
 * 改善了可回收性：我们可能会浪费一些空间，但被浪费的空间
 * 应该在请求被发出和释放时保持相当稳定。
 *
 * 对于最后一个自由列表过大的请求，通过从 malloc() 分配
 * 专用块来处理。该块仍然具有块头和
 * 块头，但当块被释放时，我们将整个块
 * 返回给 malloc()，而不是放入我们的自由列表中。
 *
 * 注意：ALLOC_MINBITS 必须足够大，以确保
 * 1<<ALLOC_MINBITS 至少为 MAXALIGN，
 * 否则我们可能无法有效对齐最小的块。
 * 8 字节对齐在目前已知的所有机器上都足够。
 *
 * 在当前参数下，请求大小高达 8K 被视为块，
 * 较大的请求则进入专用块。更改 ALLOCSET_NUM_FREELISTS
 * 以调整边界点；并调整 ALLOCSET_SEPARATE_THRESHOLD 在
 * memutils.h 中以保持一致。 （注意：在最大区块大小较小的上下文中，
 * 我们可能将 allocChunkLimit 设置为小于 8K，以避免空间浪费。）
 *--------------------
 */

#define ALLOC_MINBITS		3	/* 最小块大小为 8 字节 */
#define ALLOCSET_NUM_FREELISTS	11
#define ALLOC_CHUNK_LIMIT	(1 << (ALLOCSET_NUM_FREELISTS-1+ALLOC_MINBITS))
/* 我们使用固定大小的最大块的大小 */
#define ALLOC_CHUNK_FRACTION	4
/* 我们允许块最多占最大块大小的 1/4（减少开销） */

/*--------------------
 * 分配集的第一个分配块大小为 initBlockSize。
 * 每次我们需要分配另一个块时，我们将块大小加倍
 * （如果可能，并且不超过 maxBlockSize），以减少
 * malloc() 的管理负担。
 *
 * 但是，分配给超大块的块不遵循此规则；
 * 它们只需达到容纳该单个块所需的大小。
 *
 * 此外，如果指定了 minContextSize，
 * 第一个块的大小为该大小，
 * 然后初始块大小用于下一个块。
 *--------------------
 */

#define ALLOC_BLOCKHDRSZ	MAXALIGN(sizeof(AllocBlockData))
#define ALLOC_CHUNKHDRSZ	sizeof(struct AllocChunkData)

typedef struct AllocBlockData *AllocBlock;	/* 前向引用 */
typedef struct AllocChunkData *AllocChunk;

/*
 * AllocPointer
 *		对齐指针，可能是分配集的成员。
 */
typedef void *AllocPointer;

/*
 * AllocSetContext 是我们对 MemoryContext 的标准实现。
 *
 * 注意：header.isReset意味着没有任何事情需要 AllocSetReset 去做。
 * 这与 aset 在物理上为空（空块列表）不同，
 * 因为我们仍然会有一个保持块。它也与集合
 * 在逻辑上为空不同，因为我们不尝试检测 pfree'ing 的
 * 最后一个活动块。
 */
typedef struct AllocSetContext
{
	MemoryContextData header;	/* 标准内存上下文字段 */
	/* 有关此上下文中分配的存储的信息： */
	AllocBlock	blocks;			/* 这个集合中块的头部 */
	AllocChunk	freelist[ALLOCSET_NUM_FREELISTS];	/* 空闲块列表 */
	/* 此上下文的分配参数： */
	Size		initBlockSize;	/* 初始块大小 */
	Size		maxBlockSize;	/* 最大块大小 */
	Size		nextBlockSize;	/* 下一个要分配的块大小 */
	Size		allocChunkLimit;	/* 有效的块大小限制 */
	AllocBlock	keeper;			/* 在重置时保留此块 */
	/* 此上下文可以放入的空闲列表，或 -1 如果不是候选项： */
	int			freeListIndex;	/* 在 context_freelists[] 中的索引，或 -1 */
} AllocSetContext;

typedef AllocSetContext *AllocSet;

/*
 * AllocBlock
 *		AllocBlock 是通过 aset.c 从 malloc() 获得的内存单元。
 *		它包含一个或多个 AllocChunks，后者是 palloc() 请求的单元，
 *		并通过 pfree() 释放。AllocChunks 不能单独返回给 malloc()，
 *		相反，它们通过 pfree() 放入空闲列表，并被下一个具有
 *		匹配请求大小的 palloc() 重新使用。
 *
 *		AllocBlockData 是块的头部数据 --- 块内的可用空间
 *		从下一个对齐边界开始。
 */
typedef struct AllocBlockData
{
	AllocSet	aset;			/* 拥有此块的 aset */
	AllocBlock	prev;			/* aset 的块列表中的前一个块，如果有的话 */
	AllocBlock	next;			/* aset 的块列表中的下一个块，如果有的话 */
	char	   *freeptr;		/* 此块中空闲空间的起始位置 */
	char	   *endptr;			/* 此块中空间的结束位置 */
}			AllocBlockData;

/*
 * AllocChunk
 *		AllocBlock 中每块内存的前缀
 *
 * 注意：为了满足内存上下文 API，块的有效载荷区域必须
 * 是最大对齐的，并且 "aset" 链接必须紧邻有效载荷区域
 * （参见 GetMemoryChunkContext）。我们通过要求 sizeof(AllocChunkData)
 * 是最大对齐的来简化此模块，随后我们可以通过在
 * "aset" 字段之前添加任何所需的对齐填充来确保其正常工作。
 * 下面有一个静态断言，确保对齐是正确的。
 */
typedef struct AllocChunkData
{
	/* 大小始终是块内可用空间的大小 */
	Size		size;
#ifdef MEMORY_CONTEXT_CHECKING
	/* 当调试内存使用时，还存储实际请求的大小 */
	/* 这是空闲块中的零 */
	Size		requested_size;

#define ALLOCCHUNK_RAWSIZE  (SIZEOF_SIZE_T * 2 + SIZEOF_VOID_P)
#else
#define ALLOCCHUNK_RAWSIZE  (SIZEOF_SIZE_T + SIZEOF_VOID_P)
#endif							/* MEMORY_CONTEXT_CHECKING */

	/* 如果需要，通过添加填充来确保正确对齐 */
#if (ALLOCCHUNK_RAWSIZE % MAXIMUM_ALIGNOF) != 0
	char		padding[MAXIMUM_ALIGNOF - ALLOCCHUNK_RAWSIZE % MAXIMUM_ALIGNOF];
#endif

	/* aset 是分配的拥有 aset 或空闲时的链接 */
	void	   *aset;
	/* 此处不得有填充以达到 MAXALIGN 边界！ */
}			AllocChunkData;

/*
 * 只有 "aset" 字段应该在此模块之外被访问。
 * 在使用 valgrind 时，我们将分配块的头部的其余部分标记为 NOACCESS。
 * 但请注意，处于简化原因，空闲列表中的块头是保持可访问的。
 */
#define ALLOCCHUNK_PRIVATE_LEN	offsetof(AllocChunkData, aset)

/*
 * AllocPointerIsValid
 *		如果指针是有效的分配指针则为真。
 */
#define AllocPointerIsValid(pointer) PointerIsValid(pointer)

/*
 * AllocSetIsValid
 *		如果集合是有效的分配集合则为真。
 */
#define AllocSetIsValid(set) PointerIsValid(set)

#define AllocPointerGetChunk(ptr)	\
					((AllocChunk)(((char *)(ptr)) - ALLOC_CHUNKHDRSZ))
#define AllocChunkGetPointer(chk)	\
					((AllocPointer)(((char *)(chk)) + ALLOC_CHUNKHDRSZ))

/*
 * 与其重复创建和删除内存上下文，我们将一些
 * 空闲上下文保存在空闲列表中，以便我们可以以较少的
 * 工作将它们再次分配。在将上下文放入空闲列表之前，
 * 我们重置它，以使其仅具有初始 malloc 块而没有其他块。
 * 要成为空闲列表的候选者，上下文必须与列表中的其他
 * 上下文具有相同的 minContextSize/initBlockSize；但其
 * maxBlockSize 无关紧要，因为这不会影响初始块的大小。
 *
 * 我们目前为 ALLOCSET_DEFAULT_SIZES 上下文提供一个空闲列表，
 * 为 ALLOCSET_SMALL_SIZES 上下文提供一个空闲列表；后者
 * 由于仅 maxBlockSize 不同，也适用于 ALLOCSET_START_SMALL_SIZES。
 *
 * 通常，我们以后进先出顺序重用空闲列表上下文，
 * 希望改善引用局部性。但是，如果列表中的上下文数量过多，
 * 我们宁愿删除最近创建的上下文，以期保持进程内存映射紧凑。
 * 我们通过简单地删除所有现有条目来近似处理这种情况，
 * 假设分配大量上下文的查询可能会大致按分配的相反顺序释放它们。
 *
 * 空闲列表中的上下文通过它们的 nextchild 指针链接。
 */
#define MAX_FREE_CONTEXTS 100	/* 空闲列表长度的任意限制 */

typedef struct AllocSetFreeList
{
	int			num_free;		/* 当前列表长度 */
	AllocSetContext *first_free;	/* 列表头 */
} AllocSetFreeList;

/* context_freelists[0] 是默认参数，[1] 是小参数 */
static AllocSetFreeList context_freelists[2] =
{
	{
		0, NULL
	},
	{
		0, NULL
	}
};

/*
 * 这些函数实现了 AllocSet 上下文的 MemoryContext API。
 */
static void *fc_AllocSetAlloc(MemoryContext fc_context, Size fc_size);
static void fc_AllocSetFree(MemoryContext fc_context, void *fc_pointer);
static void *fc_AllocSetRealloc(MemoryContext fc_context, void *fc_pointer, Size fc_size);
static void fc_AllocSetReset(MemoryContext fc_context);
static void fc_AllocSetDelete(MemoryContext fc_context);
static Size fc_AllocSetGetChunkSpace(MemoryContext fc_context, void *fc_pointer);
static bool fc_AllocSetIsEmpty(MemoryContext fc_context);
static void fc_AllocSetStats(MemoryContext fc_context,
						  MemoryStatsPrintFunc fc_printfunc, void *fc_passthru,
						  MemoryContextCounters *fc_totals,
						  bool fc_print_to_stderr);

#ifdef MEMORY_CONTEXT_CHECKING
static void fc_AllocSetCheck(MemoryContext fc_context);
#endif

/*
 * 这是 AllocSet 上下文的虚拟函数表。
 */
static const MemoryContextMethods AllocSetMethods = {
	fc_AllocSetAlloc,
	fc_AllocSetFree,
	fc_AllocSetRealloc,
	fc_AllocSetReset,
	fc_AllocSetDelete,
	fc_AllocSetGetChunkSpace,
	fc_AllocSetIsEmpty,
	fc_AllocSetStats
#ifdef MEMORY_CONTEXT_CHECKING
	,fc_AllocSetCheck
#endif
};


/* ----------
 * AllocSetFreeIndex -
 *
 *		根据分配的大小计算它属于哪个空闲块
 *		的分配集合列表。调用者必须已验证
 *		大小 <= ALLOC_CHUNK_LIMIT。
 * ----------
 */
static inline int fc_AllocSetFreeIndex(Size fc_size)
{
	int			fc_idx;

	if (fc_size > (1 << ALLOC_MINBITS))
	{
		/*----------
		 * 在这个时候，我们必须计算 ceil(log2(size >> ALLOC_MINBITS))。
		 * 这与
		 *		pg_leftmost_one_pos32((size - 1) >> ALLOC_MINBITS) + 1
		 * 或等效地
		 *		pg_leftmost_one_pos32(size - 1) - ALLOC_MINBITS + 1
		 *
		 * 然而，出于额外的优化原因，我们在这里重复了
		 * 逻辑，而不是直接调用该函数。合理假设
		 * ALLOC_CHUNK_LIMIT 可以放在 16 位中，因此我们可以展开
		 * pg_leftmost_one_pos32 中的逐字节循环，只处理
		 * 最后两个字节。
		 *
		 * 是的，这个函数的热点程度值得这
		 * 么麻烦。
		 *----------
		 */
#ifdef HAVE__BUILTIN_CLZ
		fc_idx = 31 - __builtin_clz((uint32) fc_size - 1) - ALLOC_MINBITS + 1;
#else
		uint32		t,
					tsize;

		/* 静态断言我们只有一个 16 位输入值。 */
		StaticAssertStmt(ALLOC_CHUNK_LIMIT < (1 << 16),
						 "ALLOC_CHUNK_LIMIT must be less than 64kB");

		tsize = fc_size - 1;
		t = tsize >> 8;
		idx = t ? pg_leftmost_one_pos[t] + 8 : pg_leftmost_one_pos[tsize];
		idx -= ALLOC_MINBITS - 1;
#endif

		Assert(fc_idx < ALLOCSET_NUM_FREELISTS);
	}
	else
		fc_idx = 0;

	return fc_idx;
}


/*
 * 公共例程
 */


/*
 * AllocSetContextCreateInternal
 *		创建一个新的 AllocSet 上下文。
 *
 * parent: 父上下文，或者如果是顶级上下文则为 NULL
 * name: 上下文名称（必须是静态分配）
 * minContextSize: 最小上下文大小
 * initBlockSize: 初始分配块大小
 * maxBlockSize: 最大分配块大小
 *
 * 大多数调用者应该使用宏背离上下文大小参数
 * 例如 ALLOCSET_DEFAULT_SIZES。
 *
 * 注意：不要直接调用它；通过包装宏
 * AllocSetContextCreate。
 */
MemoryContext AllocSetContextCreateInternal(MemoryContext fc_parent,
							  const char *fc_name,
							  Size fc_minContextSize,
							  Size fc_initBlockSize,
							  Size fc_maxBlockSize)
{
	int			fc_freeListIndex;
	Size		fc_firstBlockSize;
	AllocSet	fc_set;
	AllocBlock	fc_block;

	/* 断言我们正确填充了 AllocChunkData */
	StaticAssertStmt(ALLOC_CHUNKHDRSZ == MAXALIGN(ALLOC_CHUNKHDRSZ),
					 "sizeof(AllocChunkData) is not maxaligned");
	StaticAssertStmt(offsetof(AllocChunkData, aset) + sizeof(MemoryContext) ==
					 ALLOC_CHUNKHDRSZ,
					 "padding calculation in AllocChunkData is wrong");

	/*
	 * 首先，验证分配参数。以前这些是常规运行时
	 * 测试和 elog，实际上断言似乎足够，因为没有人
	 * 在运行时更改他们的参数。我们有些随意地强制最小
	 * 1K 块大小。
	 */
	Assert(fc_initBlockSize == MAXALIGN(fc_initBlockSize) &&
		   fc_initBlockSize >= 1024);
	Assert(fc_maxBlockSize == MAXALIGN(fc_maxBlockSize) &&
		   fc_maxBlockSize >= fc_initBlockSize &&
		   AllocHugeSizeIsValid(fc_maxBlockSize)); /* 必须能够安全地加倍 */
	Assert(fc_minContextSize == 0 ||
		   (fc_minContextSize == MAXALIGN(fc_minContextSize) &&
			fc_minContextSize >= 1024 &&
			fc_minContextSize <= fc_maxBlockSize));

	/*
	 * 检查参数是否匹配任何可用的空闲列表。我们不需要
	 * 强调 maxBlockSize 的匹配。
	 */
	if (fc_minContextSize == ALLOCSET_DEFAULT_MINSIZE &&
		fc_initBlockSize == ALLOCSET_DEFAULT_INITSIZE)
		fc_freeListIndex = 0;
	else if (fc_minContextSize == ALLOCSET_SMALL_MINSIZE &&
			 fc_initBlockSize == ALLOCSET_SMALL_INITSIZE)
		fc_freeListIndex = 1;
	else
		fc_freeListIndex = -1;

	/*
	 * 如果存在合适的空闲列表条目，只需回收该上下文。
	 */
	if (fc_freeListIndex >= 0)
	{
		AllocSetFreeList *fc_freelist = &context_freelists[fc_freeListIndex];

		if (fc_freelist->first_free != NULL)
		{
			/* 从空闲列表中移除条目 */
			fc_set = fc_freelist->first_free;
			fc_freelist->first_free = (AllocSet) fc_set->header.nextchild;
			fc_freelist->num_free--;

			/* 更新其 maxBlockSize；其他一切应该没问题 */
			fc_set->maxBlockSize = fc_maxBlockSize;

			/* 重新初始化其头部，安装正确的名称和父级 */
			MemoryContextCreate((MemoryContext) fc_set,
								T_AllocSetContext,
								&AllocSetMethods,
								fc_parent,
								fc_name);

			((MemoryContext) fc_set)->mem_allocated =
				fc_set->keeper->endptr - ((char *) fc_set);

			return (MemoryContext) fc_set;
		}
	}

	/* 确定初始块的大小 */
	fc_firstBlockSize = MAXALIGN(sizeof(AllocSetContext)) +
		ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
	if (fc_minContextSize != 0)
		fc_firstBlockSize = Max(fc_firstBlockSize, fc_minContextSize);
	else
		fc_firstBlockSize = Max(fc_firstBlockSize, fc_initBlockSize);

	/*
	 * 分配初始块。与其他 aset.c 块不同，它从
	 * 上下文头部开始，其块头跟随在后。
	 */
	fc_set = (AllocSet) malloc(fc_firstBlockSize);
	if (fc_set == NULL)
	{
		if (TopMemoryContext)
			MemoryContextStats(TopMemoryContext);
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory"),
				 errdetail("Failed while creating memory context \"%s\".",
						   fc_name)));
	}

	/*
	 * 避免编写可能在此和 MemoryContextCreate 之间失败的代码；
	 * 如果我们在此过程中报告错误，我们将泄漏头部/初始块。
	 */

	/* 填充初始块的块头 */
	fc_block = (AllocBlock) (((char *) fc_set) + MAXALIGN(sizeof(AllocSetContext)));
	fc_block->aset = fc_set;
	fc_block->freeptr = ((char *) fc_block) + ALLOC_BLOCKHDRSZ;
	fc_block->endptr = ((char *) fc_set) + fc_firstBlockSize;
	fc_block->prev = NULL;
	fc_block->next = NULL;

	/* 将未分配的空间标记为 NOACCESS；保持块头不变。 */
	VALGRIND_MAKE_MEM_NOACCESS(block->freeptr, block->endptr - block->freeptr);

	/* 将块记住为块列表的一部分 */
	fc_set->blocks = fc_block;
	/* 将块标记为在重置时不应释放 */
	fc_set->keeper = fc_block;

	/* 完成填充上下文头部的 aset 特定部分 */
	MemSetAligned(fc_set->freelist, 0, sizeof(fc_set->freelist));

	fc_set->initBlockSize = fc_initBlockSize;
	fc_set->maxBlockSize = fc_maxBlockSize;
	fc_set->nextBlockSize = fc_initBlockSize;
	fc_set->freeListIndex = fc_freeListIndex;

	/*
	 * 计算此上下文的分配块大小限制。由于固定数量的
	 * 空闲列表，它不能超过 ALLOC_CHUNK_LIMIT。
	 * 如果 maxBlockSize 较小，则超过 maxBlockSize 的请求，或者
	 * 甚至是其重要的一部分，也应视为大块。
	 * 对于 maxBlockSize 是 2 的幂的典型情况，块大小
	 * 限制将至多为 maxBlockSize 的 1/8，因此，给定的请求流
	 * 都是最大块大小，我们将最多浪费
	 * 分配空间的 1/8。
	 *
	 * 我们必须使 allocChunkLimit 是 2 的幂，因为任何块的
	 * 请求和实际分配大小必须在限制的同一侧，
	 * 否则我们就会对块是否为“大”感到困惑。
	 *
	 * 此外，allocChunkLimit 不能超过 ALLOCSET_SEPARATE_THRESHOLD。
	 */
	StaticAssertStmt(ALLOC_CHUNK_LIMIT == ALLOCSET_SEPARATE_THRESHOLD,
					 "ALLOC_CHUNK_LIMIT != ALLOCSET_SEPARATE_THRESHOLD");

	fc_set->allocChunkLimit = ALLOC_CHUNK_LIMIT;
	while ((Size) (fc_set->allocChunkLimit + ALLOC_CHUNKHDRSZ) >
		   (Size) ((fc_maxBlockSize - ALLOC_BLOCKHDRSZ) / ALLOC_CHUNK_FRACTION))
		fc_set->allocChunkLimit >>= 1;

	/* 最后，执行上下文创建的类型无关部分 */
	MemoryContextCreate((MemoryContext) fc_set,
						T_AllocSetContext,
						&AllocSetMethods,
						fc_parent,
						fc_name);

	((MemoryContext) fc_set)->mem_allocated = fc_firstBlockSize;

	return (MemoryContext) fc_set;
}

/*
 * AllocSetReset
 *		释放在给定集合中分配的所有内存。
 *
 * 实际上，这个例程在该做什么方面有一些自由裁量权。
 * 它应标记所有分配的块为已释放，但不一定
 * 需要归还集合拥有的所有资源。我们的实际实现是
 * 归还除了“保留”块（必须保留，因为它与
 * 上下文头共享一个 malloc 块）之外的所有内容。
 * 通过这种方式，我们不会在小分配后多次重置上下文时
 * 垃圾 malloc()，这对于每个元组上下文来说是典型的行为。
 */
static void fc_AllocSetReset(MemoryContext fc_context)
{
	AllocSet	fc_set = (AllocSet) fc_context;
	AllocBlock	fc_block;
	Size		fc_keepersize PG_USED_FOR_ASSERTS_ONLY
	= fc_set->keeper->endptr - ((char *) fc_set);

	AssertArg(AllocSetIsValid(fc_set));

#ifdef MEMORY_CONTEXT_CHECKING
	/* 在释放前检查损坏和泄漏 */
	fc_AllocSetCheck(fc_context);
#endif

	/* 清除块空闲列表 */
	MemSetAligned(fc_set->freelist, 0, sizeof(fc_set->freelist));

	fc_block = fc_set->blocks;

	/* 新的块列表将仅是保留块 */
	fc_set->blocks = fc_set->keeper;

	while (fc_block != NULL)
	{
		AllocBlock	fc_next = fc_block->next;

		if (fc_block == fc_set->keeper)
		{
			/* 重置块，但不将其返回给malloc */
			char	   *fc_datastart = ((char *) fc_block) + ALLOC_BLOCKHDRSZ;

#ifdef CLOBBER_FREED_MEMORY
			wipe_mem(fc_datastart, fc_block->freeptr - fc_datastart);
#else
			/* wipe_mem()本来会做这个 */
			VALGRIND_MAKE_MEM_NOACCESS(datastart, block->freeptr - datastart);
#endif
			fc_block->freeptr = fc_datastart;
			fc_block->prev = NULL;
			fc_block->next = NULL;
		}
		else
		{
			/* 正常情况，释放块 */
			fc_context->mem_allocated -= fc_block->endptr - ((char *) fc_block);

#ifdef CLOBBER_FREED_MEMORY
			wipe_mem(fc_block, fc_block->freeptr - ((char *) fc_block));
#endif
			free(fc_block);
		}
		fc_block = fc_next;
	}

	Assert(fc_context->mem_allocated == fc_keepersize);

	/* 也重置块大小分配序列 */
	fc_set->nextBlockSize = fc_set->initBlockSize;
}

/* 
 * AllocSetDelete
 * 		释放在给定集合中分配的所有内存，
 * 		以准备删除该集合。
 *
 * 与AllocSetReset不同，这个*必须*释放该集合的所有资源。
 */
static void fc_AllocSetDelete(MemoryContext fc_context)
{
	AllocSet	fc_set = (AllocSet) fc_context;
	AllocBlock	fc_block = fc_set->blocks;
	Size		fc_keepersize PG_USED_FOR_ASSERTS_ONLY
	= fc_set->keeper->endptr - ((char *) fc_set);

	AssertArg(AllocSetIsValid(fc_set));

#ifdef MEMORY_CONTEXT_CHECKING
	/* 在释放前检查损坏和泄漏 */
	fc_AllocSetCheck(fc_context);
#endif

	/* 
	 * 如果上下文是自由列表的候选者，将其放入该自由列表中
	 * 而不是销毁它。
 */
	if (fc_set->freeListIndex >= 0)
	{
		AllocSetFreeList *fc_freelist = &context_freelists[fc_set->freeListIndex];

		/* 
		 * 如果需要，重置上下文，以便我们不会挂着
		 * 超过初始malloc块的内容。
 */
		if (!fc_context->isReset)
			MemoryContextResetOnly(fc_context);

		/* 
		 * 如果自由列表已满，只需舍弃其中的内容。见
		 * context_freelists[]中的注释。
 */
		if (fc_freelist->num_free >= MAX_FREE_CONTEXTS)
		{
			while (fc_freelist->first_free != NULL)
			{
				AllocSetContext *fc_oldset = fc_freelist->first_free;

				fc_freelist->first_free = (AllocSetContext *) fc_oldset->header.nextchild;
				fc_freelist->num_free--;

				/* 仅剩下释放头部/初始块 */
				free(fc_oldset);
			}
			Assert(fc_freelist->num_free == 0);
		}

		/* 现在将刚删除的上下文添加到自由列表中。 */
		fc_set->header.nextchild = (MemoryContext) fc_freelist->first_free;
		fc_freelist->first_free = fc_set;
		fc_freelist->num_free++;

		return;
	}

	/* 释放所有块，除了作为上下文头部一部分的保留块 */
	while (fc_block != NULL)
	{
		AllocBlock	fc_next = fc_block->next;

		if (fc_block != fc_set->keeper)
			fc_context->mem_allocated -= fc_block->endptr - ((char *) fc_block);

#ifdef CLOBBER_FREED_MEMORY
		wipe_mem(fc_block, fc_block->freeptr - ((char *) fc_block));
#endif

		if (fc_block != fc_set->keeper)
			free(fc_block);

		fc_block = fc_next;
	}

	Assert(fc_context->mem_allocated == fc_keepersize);

	/* 最后，释放上下文头部，包括保留块 */
	free(fc_set);
}

/* 
 * AllocSetAlloc
 * 		返回给定大小已分配内存的指针，如果请求无法完成，则返回NULL；
 * 		内存将被添加到集合中。
 *
 * 没有请求可以超过：
 * 		MAXALIGN_DOWN(SIZE_MAX) - ALLOC_BLOCKHDRSZ - ALLOC_CHUNKHDRSZ
 * 所有调用者使用一个更低的限制。
 *
 * 注意：使用valgrind时，返回的分配如何标记并不重要，因为mcxt.c会将其设置为UNDEFINED。在某些路径中，我们将返回标记为NOACCESS的空间 - AllocSetRealloc需要小心！
 */
static void * fc_AllocSetAlloc(MemoryContext fc_context, Size fc_size)
{
	AllocSet	fc_set = (AllocSet) fc_context;
	AllocBlock	fc_block;
	AllocChunk	fc_chunk;
	int			fc_fidx;
	Size		fc_chunk_size;
	Size		fc_blksize;

	AssertArg(AllocSetIsValid(fc_set));

	/* 
	 * 如果请求的大小超过块的最大值，则为此请求分配整个块。
 */
	if (fc_size > fc_set->allocChunkLimit)
	{
		fc_chunk_size = MAXALIGN(fc_size);
		fc_blksize = fc_chunk_size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
		fc_block = (AllocBlock) malloc(fc_blksize);
		if (fc_block == NULL)
			return NULL;

		fc_context->mem_allocated += fc_blksize;

		fc_block->aset = fc_set;
		fc_block->freeptr = fc_block->endptr = ((char *) fc_block) + fc_blksize;

		fc_chunk = (AllocChunk) (((char *) fc_block) + ALLOC_BLOCKHDRSZ);
		fc_chunk->aset = fc_set;
		fc_chunk->size = fc_chunk_size;
#ifdef MEMORY_CONTEXT_CHECKING
		fc_chunk->requested_size = fc_size;
		/* 设置标记以捕捉“未使用”空间的覆盖 */
		if (fc_size < fc_chunk_size)
			set_sentinel(AllocChunkGetPointer(fc_chunk), fc_size);
#endif
#ifdef RANDOMIZE_ALLOCATED_MEMORY
		/* 用垃圾填充分配的空间 */
		randomize_mem((char *) AllocChunkGetPointer(fc_chunk), fc_size);
#endif

		/* 
		 * 如果有活动分配块，则将新块放在其下方，
		 * 以便我们不会失去其中剩余空间的使用。
 */
		if (fc_set->blocks != NULL)
		{
			fc_block->prev = fc_set->blocks;
			fc_block->next = fc_set->blocks->next;
			if (fc_block->next)
				fc_block->next->prev = fc_block;
			fc_set->blocks->next = fc_block;
		}
		else
		{
			fc_block->prev = NULL;
			fc_block->next = NULL;
			fc_set->blocks = fc_block;
		}

		/* 确保任何填充字节标记为NOACCESS。 */
		VALGRIND_MAKE_MEM_NOACCESS((char *) AllocChunkGetPointer(fc_chunk) + fc_size,
								   chunk_size - fc_size);

		/* 禁止外部访问块头的私有部分。 */
		VALGRIND_MAKE_MEM_NOACCESS(fc_chunk, ALLOCCHUNK_PRIVATE_LEN);

		return AllocChunkGetPointer(fc_chunk);
	}

	/* 
	 * 请求足够小，可以视为一个块。查看
	 * 相应的自由列表，看看是否有可以重用的自由块。
	 * 如果找到一个，将其从自由列表中移除，重新成为
	 * 分配集合的成员并返回其数据地址。
 */
	fc_fidx = fc_AllocSetFreeIndex(fc_size);
	fc_chunk = fc_set->freelist[fc_fidx];
	if (fc_chunk != NULL)
	{
		Assert(fc_chunk->size >= fc_size);

		fc_set->freelist[fc_fidx] = (AllocChunk) fc_chunk->aset;

		fc_chunk->aset = (void *) fc_set;

#ifdef MEMORY_CONTEXT_CHECKING
		fc_chunk->requested_size = fc_size;
		/* 设置标记以捕捉“未使用”空间的覆盖 */
		if (fc_size < fc_chunk->size)
			set_sentinel(AllocChunkGetPointer(fc_chunk), fc_size);
#endif
#ifdef RANDOMIZE_ALLOCATED_MEMORY
		/* 用垃圾填充分配的空间 */
		randomize_mem((char *) AllocChunkGetPointer(fc_chunk), fc_size);
#endif

		/* 确保任何填充字节标记为NOACCESS。 */
		VALGRIND_MAKE_MEM_NOACCESS((char *) AllocChunkGetPointer(fc_chunk) + fc_size,
								   fc_chunk->size - fc_size);

		/* 禁止外部访问块头的私有部分。 */
		VALGRIND_MAKE_MEM_NOACCESS(fc_chunk, ALLOCCHUNK_PRIVATE_LEN);

		return AllocChunkGetPointer(fc_chunk);
	}

	/* 
	 * 选择要分配的实际块大小。
 */
	fc_chunk_size = (1 << ALLOC_MINBITS) << fc_fidx;
	Assert(fc_chunk_size >= fc_size);

	/* 
	 * 如果活动分配块中有足够的空间，我们将把块放入该块中。
	 * 否则必须开启一个新块。
 */
	if ((fc_block = fc_set->blocks) != NULL)
	{
		Size		fc_availspace = fc_block->endptr - fc_block->freeptr;

		if (fc_availspace < (fc_chunk_size + ALLOC_CHUNKHDRSZ))
		{
			/* 
			 * 现有的活动（顶部）块没有足够的空间满足
			 * 请求的分配，但它可能仍然有有用的
			 * 空间。一旦我们在块列表中向下推它，
			 * 我们将永远不会尝试从中分配更多空间。所以，在我们
			 * 这样做之前，将其空闲空间划分为可以放在
			 * 集合的自由列表中的块。
			 *
			 * 因为我们只能在块中剩余小于
			 * ALLOC_CHUNK_LIMIT的情况下到达这里，所以这个循环不能迭代
			 * 超过ALLOCSET_NUM_FREELISTS-1次。
 */
			while (fc_availspace >= ((1 << ALLOC_MINBITS) + ALLOC_CHUNKHDRSZ))
			{
				Size		fc_availchunk = fc_availspace - ALLOC_CHUNKHDRSZ;
				int			fc_a_fidx = fc_AllocSetFreeIndex(fc_availchunk);

				/* 
				 * 在大多数情况下，我们会获得下一个更大的
				 * 自由列表的索引，而不是我们需要将此块放入的那个。
				 * 唯一的例外是当availchunk恰好是2的幂时。
 */
				if (fc_availchunk != ((Size) 1 << (fc_a_fidx + ALLOC_MINBITS)))
				{
					fc_a_fidx--;
					Assert(fc_a_fidx >= 0);
					fc_availchunk = ((Size) 1 << (fc_a_fidx + ALLOC_MINBITS));
				}

				fc_chunk = (AllocChunk) (fc_block->freeptr);

				/* 准备初始化块头。 */
				VALGRIND_MAKE_MEM_UNDEFINED(fc_chunk, ALLOC_CHUNKHDRSZ);

				fc_block->freeptr += (fc_availchunk + ALLOC_CHUNKHDRSZ);
				fc_availspace -= (fc_availchunk + ALLOC_CHUNKHDRSZ);

				fc_chunk->size = fc_availchunk;
#ifdef MEMORY_CONTEXT_CHECKING
				fc_chunk->requested_size = 0;	/* 标记为free */
#endif
				fc_chunk->aset = (void *) fc_set->freelist[fc_a_fidx];
				fc_set->freelist[fc_a_fidx] = fc_chunk;
			}

			/* 标记需要创建一个新块 */
			fc_block = NULL;
		}
	}

	/* 
	 * 是时候创建一个新的常规（多个块）块了吗？
 */
	if (fc_block == NULL)
	{
		Size		fc_required_size;

		/* 
		 * 第一个这样的块的大小为initBlockSize，并且我们在每个随后的块中将空间翻倍，
		 * 但不超过maxBlockSize。
 */
		fc_blksize = fc_set->nextBlockSize;
		fc_set->nextBlockSize <<= 1;
		if (fc_set->nextBlockSize > fc_set->maxBlockSize)
			fc_set->nextBlockSize = fc_set->maxBlockSize;

		/*
		 * 如果 initBlockSize 小于 ALLOC_CHUNK_LIMIT，我们可能需要更多的
		 * 空间...但尽量保持为 2 的幂。
		 */
		fc_required_size = fc_chunk_size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
		while (fc_blksize < fc_required_size)
			fc_blksize <<= 1;

		/* 尝试进行分配 */
		fc_block = (AllocBlock) malloc(fc_blksize);

		/*
		 * 我们可能在这里请求相当大的块，所以如果 malloc 失败，需
		 * 要能够应对。但如果可用空间少于 1 MB 或更少，就放弃...
		 */
		while (fc_block == NULL && fc_blksize > 1024 * 1024)
		{
			fc_blksize >>= 1;
			if (fc_blksize < fc_required_size)
				break;
			fc_block = (AllocBlock) malloc(fc_blksize);
		}

		if (fc_block == NULL)
			return NULL;

		fc_context->mem_allocated += fc_blksize;

		fc_block->aset = fc_set;
		fc_block->freeptr = ((char *) fc_block) + ALLOC_BLOCKHDRSZ;
		fc_block->endptr = ((char *) fc_block) + fc_blksize;

		/* 将未分配的空间标记为 NOACCESS。 */
		VALGRIND_MAKE_MEM_NOACCESS(block->freeptr,
								   blksize - ALLOC_BLOCKHDRSZ);

		fc_block->prev = NULL;
		fc_block->next = fc_set->blocks;
		if (fc_block->next)
			fc_block->next->prev = fc_block;
		fc_set->blocks = fc_block;
	}

	/*
	 * 好的，进行分配
	 */
	fc_chunk = (AllocChunk) (fc_block->freeptr);

	/* 准备初始化块头。 */
	VALGRIND_MAKE_MEM_UNDEFINED(fc_chunk, ALLOC_CHUNKHDRSZ);

	fc_block->freeptr += (fc_chunk_size + ALLOC_CHUNKHDRSZ);
	Assert(fc_block->freeptr <= fc_block->endptr);

	fc_chunk->aset = (void *) fc_set;
	fc_chunk->size = fc_chunk_size;
#ifdef MEMORY_CONTEXT_CHECKING
	fc_chunk->requested_size = fc_size;
	/* 设置标记以捕捉“未使用”空间的覆盖 */
	if (fc_size < fc_chunk->size)
		set_sentinel(AllocChunkGetPointer(fc_chunk), fc_size);
#endif
#ifdef RANDOMIZE_ALLOCATED_MEMORY
	/* 用垃圾填充分配的空间 */
	randomize_mem((char *) AllocChunkGetPointer(fc_chunk), fc_size);
#endif

	/* 确保任何填充字节标记为NOACCESS。 */
	VALGRIND_MAKE_MEM_NOACCESS((char *) AllocChunkGetPointer(fc_chunk) + fc_size,
							   chunk_size - fc_size);

	/* 禁止外部访问块头的私有部分。 */
	VALGRIND_MAKE_MEM_NOACCESS(fc_chunk, ALLOCCHUNK_PRIVATE_LEN);

	return AllocChunkGetPointer(fc_chunk);
}

/*
 * AllocSetFree
 *		释放已分配的内存；内存从集合中移除。
 */
static void fc_AllocSetFree(MemoryContext fc_context, void *fc_pointer)
{
	AllocSet	fc_set = (AllocSet) fc_context;
	AllocChunk	fc_chunk = AllocPointerGetChunk(fc_pointer);

	/* 允许访问块头的私有部分。 */
	VALGRIND_MAKE_MEM_DEFINED(fc_chunk, ALLOCCHUNK_PRIVATE_LEN);

#ifdef MEMORY_CONTEXT_CHECKING
	/* 测试是否有人在块的未使用空间上乱写 */
	if (fc_chunk->requested_size < fc_chunk->size)
		if (!sentinel_ok(fc_pointer, fc_chunk->requested_size))
			elog(WARNING, "detected write past chunk end in %s %p",
				 fc_set->header.name, fc_chunk);
#endif

	if (fc_chunk->size > fc_set->allocChunkLimit)
	{
		/*
		 * 大块一定是作为单块分配的。只需解除链接该块并返回
		 * 给 malloc()。
		 */
		AllocBlock	fc_block = (AllocBlock) (((char *) fc_chunk) - ALLOC_BLOCKHDRSZ);

		/*
		 * 尝试验证我们是否有一个合理的块指针：它应该引用
		 * 正确的 aset，freeptr 和 endptr 应该指向块的末尾。
		 */
		if (fc_block->aset != fc_set ||
			fc_block->freeptr != fc_block->endptr ||
			fc_block->freeptr != ((char *) fc_block) +
			(fc_chunk->size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ))
			elog(ERROR, "could not find block containing chunk %p", fc_chunk);

		/* 好的，从 aset 的列表中移除块并释放它 */
		if (fc_block->prev)
			fc_block->prev->next = fc_block->next;
		else
			fc_set->blocks = fc_block->next;
		if (fc_block->next)
			fc_block->next->prev = fc_block->prev;

		fc_context->mem_allocated -= fc_block->endptr - ((char *) fc_block);

#ifdef CLOBBER_FREED_MEMORY
		wipe_mem(fc_block, fc_block->freeptr - ((char *) fc_block));
#endif
		free(fc_block);
	}
	else
	{
		/* 正常情况，将块放入适当的空闲列表中 */
		int			fc_fidx = fc_AllocSetFreeIndex(fc_chunk->size);

		fc_chunk->aset = (void *) fc_set->freelist[fc_fidx];

#ifdef CLOBBER_FREED_MEMORY
		wipe_mem(fc_pointer, fc_chunk->size);
#endif

#ifdef MEMORY_CONTEXT_CHECKING
		/* 在空闲列表上的块中将 requested_size 重置为 0 */
		fc_chunk->requested_size = 0;
#endif
		fc_set->freelist[fc_fidx] = fc_chunk;
	}
}

/*
 * AllocSetRealloc
 *		返回指向给定大小的已分配内存的新指针，如果请求
 *		无法完成则返回 NULL；这块内存被添加到集合中。
 *		与给定指针关联的内存被复制到新的内存中，
 *		旧内存被释放。
 *
 * 在没有 MEMORY_CONTEXT_CHECKING 的情况下，我们不知道旧的请求大小。
 * 这使得我们的 Valgrind 客户端请求不够精确，可能会出现错误的阴性。
 * （原则上，我们可以使用 VALGRIND_GET_VBITS() 重新发现旧的
 * 请求大小。）
 */
static void * fc_AllocSetRealloc(MemoryContext fc_context, void *fc_pointer, Size fc_size)
{
	AllocSet	fc_set = (AllocSet) fc_context;
	AllocChunk	fc_chunk = AllocPointerGetChunk(fc_pointer);
	Size		fc_oldchksize;

	/* 允许访问块头的私有部分。 */
	VALGRIND_MAKE_MEM_DEFINED(fc_chunk, ALLOCCHUNK_PRIVATE_LEN);

	fc_oldchksize = fc_chunk->size;

#ifdef MEMORY_CONTEXT_CHECKING
	/* 测试是否有人在块的未使用空间上乱写 */
	if (fc_chunk->requested_size < fc_oldchksize)
		if (!sentinel_ok(fc_pointer, fc_chunk->requested_size))
			elog(WARNING, "detected write past chunk end in %s %p",
				 fc_set->header.name, fc_chunk);
#endif

	if (fc_oldchksize > fc_set->allocChunkLimit)
	{
		/*
		 * 该块必须作为单块分配。使用 realloc() 来使包含的块变
		 * 大或变小，尽量减少空间浪费。
		 */
		AllocBlock	fc_block = (AllocBlock) (((char *) fc_chunk) - ALLOC_BLOCKHDRSZ);
		Size		fc_chksize;
		Size		fc_blksize;
		Size		fc_oldblksize;

		/*
		 * 尝试验证我们是否有一个合理的块指针：它应该引用
		 * 正确的 aset，freeptr 和 endptr 应该指向块的末尾。
		 */
		if (fc_block->aset != fc_set ||
			fc_block->freeptr != fc_block->endptr ||
			fc_block->freeptr != ((char *) fc_block) +
			(fc_oldchksize + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ))
			elog(ERROR, "could not find block containing chunk %p", fc_chunk);

		/*
		 * 即使新的请求小于 set->allocChunkLimit，我们仍坚持
		 * 单块分配的方法。因此我们需要 chunk->size 大于
		 * set->allocChunkLimit，以免在未来的调用中对块的状态感到困惑。
		 */
		fc_chksize = Max(fc_size, fc_set->allocChunkLimit + 1);
		fc_chksize = MAXALIGN(fc_chksize);

		/* 进行 realloc */
		fc_blksize = fc_chksize + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
		fc_oldblksize = fc_block->endptr - ((char *) fc_block);

		fc_block = (AllocBlock) realloc(fc_block, fc_blksize);
		if (fc_block == NULL)
		{
			/* 禁止外部访问块头的私有部分。 */
			VALGRIND_MAKE_MEM_NOACCESS(fc_chunk, ALLOCCHUNK_PRIVATE_LEN);
			return NULL;
		}

		/* 另外更新，避免在 (oldblksize > blksize) 时发生下溢 */
		fc_context->mem_allocated -= fc_oldblksize;
		fc_context->mem_allocated += fc_blksize;

		fc_block->freeptr = fc_block->endptr = ((char *) fc_block) + fc_blksize;

		/* 更新指针，因为块可能已被移动 */
		fc_chunk = (AllocChunk) (((char *) fc_block) + ALLOC_BLOCKHDRSZ);
		fc_pointer = AllocChunkGetPointer(fc_chunk);
		if (fc_block->prev)
			fc_block->prev->next = fc_block;
		else
			fc_set->blocks = fc_block;
		if (fc_block->next)
			fc_block->next->prev = fc_block;
		fc_chunk->size = fc_chksize;

#ifdef MEMORY_CONTEXT_CHECKING
#ifdef RANDOMIZE_ALLOCATED_MEMORY

		/*
		 * 我们只能在知道先前请求的情况下随机化额外空间。
		 * 使用 Valgrind 时，randomize_mem() 也将内存标记为 UNDEFINED。
		 */
		if (fc_size > fc_chunk->requested_size)
			randomize_mem((char *) fc_pointer + fc_chunk->requested_size,
						  fc_size - fc_chunk->requested_size);
#else

		/*
		 * 如果这是一个增加，realloc() 将标记任何新分配的部分
		 * （从 oldchksize 到 chksize）为 UNDEFINED，但我们
		 * 还需要调整旧分配的尾部字节（从 chunk->requested_size 到
		 * oldchksize），因为它们被标记为 NOACCESS。
		 * 确保不要标记太多字节，以防 chunk->requested_size
		 * < size < oldchksize。
		 */
#ifdef USE_VALGRIND
		if (Min(fc_size, fc_oldchksize) > fc_chunk->requested_size)
			VALGRIND_MAKE_MEM_UNDEFINED((char *) fc_pointer + fc_chunk->requested_size,
										Min(fc_size, fc_oldchksize) - fc_chunk->requested_size);
#endif
#endif

		fc_chunk->requested_size = fc_size;

		/* 设置标记以捕捉“未使用”空间的覆盖 */
		if (fc_size < fc_chunk->size)
			set_sentinel(fc_pointer, fc_size);
#else							/* !MEMORY_CONTEXT_CHECKING */

		/*
		 * 我们可能需要调整旧分配的字节标记，因为其中一些可能
		 * 被标记为 NOACCESS。我们不知道旧块大小有多少是请求的大小；
		 * 它可能小到只有一个字节。我们必须谨慎，只将整个旧部分标记
		 * 为 DEFINED。确保不要在新分配的内存之外进行标记，以防
		 * 新分配的内存小于旧分配的内存。
		 */
		VALGRIND_MAKE_MEM_DEFINED(fc_pointer, Min(fc_size, fc_oldchksize));
#endif

		/* 确保任何填充字节标记为NOACCESS。 */
		VALGRIND_MAKE_MEM_NOACCESS((char *) fc_pointer + fc_size, chksize - fc_size);

		/* 禁止外部访问块头的私有部分。 */
		VALGRIND_MAKE_MEM_NOACCESS(fc_chunk, ALLOCCHUNK_PRIVATE_LEN);

		return fc_pointer;
	}

	/*
	 * 块大小在 AllocSetAlloc() 中与 2 的幂对齐。也许分配区域
	 * 已经大于或等于新的大小。（特别是，如果请求的大小减少，
	 * 我们将在这里退出。）
	 */
	else if (fc_oldchksize >= fc_size)
	{
#ifdef MEMORY_CONTEXT_CHECKING
		Size		fc_oldrequest = fc_chunk->requested_size;

#ifdef RANDOMIZE_ALLOCATED_MEMORY
		/* 只有在知道先前请求的情况下才能填充额外的空间 */
		if (fc_size > fc_oldrequest)
			randomize_mem((char *) fc_pointer + fc_oldrequest,
						  fc_size - fc_oldrequest);
#endif

		fc_chunk->requested_size = fc_size;

		/*
		 * 如果这是一个增加，标记任何新可用部分为 UNDEFINED。
		 * 否则，标记过时部分为 NOACCESS。
		 */
		if (fc_size > fc_oldrequest)
			VALGRIND_MAKE_MEM_UNDEFINED((char *) fc_pointer + fc_oldrequest,
										fc_size - fc_oldrequest);
		else
			VALGRIND_MAKE_MEM_NOACCESS((char *) fc_pointer + fc_size,
									   fc_oldchksize - fc_size);

		/* 设置标记以捕捉“未使用”空间的覆盖 */
		if (fc_size < fc_oldchksize)
			set_sentinel(fc_pointer, fc_size);
#else							/* !MEMORY_CONTEXT_CHECKING */

		/*
		 * 我们没有信息来确定是增长旧请求还是缩小它，因此
		 * 我们保守地将整个新分配标记为 DEFINED。
		 */
		VALGRIND_MAKE_MEM_NOACCESS(fc_pointer, fc_oldchksize);
		VALGRIND_MAKE_MEM_DEFINED(fc_pointer, fc_size);
#endif

		/* 禁止外部访问块头的私有部分。 */
		VALGRIND_MAKE_MEM_NOACCESS(fc_chunk, ALLOCCHUNK_PRIVATE_LEN);

		return fc_pointer;
	}
	else
	{
		/*
		 * 扩大小块的情况。我们仅通过粗暴的方式来做到这一点，
		 * 即分配一个新块并复制数据。由于我们知道现有数据不大，
		 * 所以这不会涉及任何重大 memcpy 开销，因此没有必要更聪明。
		 * （曾经我们试图在可以就地扩大块时避免 memcpy，然而
		 * 这在 palloc/repalloc/pfree 的重复周期中表现不佳：
		 * 最终释放的块进入错误的空闲列表，导致下一个初始 palloc
		 * 请求泄漏内存，泄漏时间无穷无尽。请参见 2007-08-11 的
		 * pgsql-hackers 归档。）
		 */
		AllocPointer fc_newPointer;
		Size		fc_oldsize;

		/* 分配新块 */
		fc_newPointer = fc_AllocSetAlloc((MemoryContext) fc_set, fc_size);

		/* 如果请求未完成则立即返回 */
		if (fc_newPointer == NULL)
		{
			/* 禁止外部访问块头的私有部分。 */
			VALGRIND_MAKE_MEM_NOACCESS(fc_chunk, ALLOCCHUNK_PRIVATE_LEN);
			return NULL;
		}

		/*
		 * AllocSetAlloc() 可能返回仍然为 NOACCESS 的区域。
		 * 目前将其改为 UNDEFINED；然后 memcpy() 将传递
		 * 已定义性从旧分配到新分配。如果我们知道旧
		 * 分配，仅复制那么多。否则，将整个旧
		 * 块设置为定义，以避免在复制当前的 NOACCESS
		 * 尾随字节时发生错误。
		 */
		VALGRIND_MAKE_MEM_UNDEFINED(newPointer, fc_size);
#ifdef MEMORY_CONTEXT_CHECKING
		fc_oldsize = fc_chunk->requested_size;
#else
		fc_oldsize = fc_oldchksize;
		VALGRIND_MAKE_MEM_DEFINED(fc_pointer, fc_oldsize);
#endif

		/* 转移现有数据（一定能够适应） */
		memcpy(fc_newPointer, fc_pointer, fc_oldsize);

		/* 释放旧块 */
		fc_AllocSetFree((MemoryContext) fc_set, fc_pointer);

		return fc_newPointer;
	}
}

/*
 * AllocSetGetChunkSpace
 *		给定一个当前已分配的块，确定它占用的总空间
 *		（包括所有内存分配开销）。
 */
static Size fc_AllocSetGetChunkSpace(MemoryContext fc_context, void *fc_pointer)
{
	AllocChunk	fc_chunk = AllocPointerGetChunk(fc_pointer);
	Size		fc_result;

	VALGRIND_MAKE_MEM_DEFINED(fc_chunk, ALLOCCHUNK_PRIVATE_LEN);
	fc_result = fc_chunk->size + ALLOC_CHUNKHDRSZ;
	VALGRIND_MAKE_MEM_NOACCESS(fc_chunk, ALLOCCHUNK_PRIVATE_LEN);
	return fc_result;
}

/*
 * AllocSetIsEmpty
 *		一个 allocset 是否没有分配的空间？
 */
static bool fc_AllocSetIsEmpty(MemoryContext fc_context)
{
	/*
	 * 目前，我们仅在上下文是新创建或刚刚重置时
	 * 说“空”。我们可以检查空闲列表，以确定
	 * 是否所有空间都已被释放，但对于目前该
	 * 功能的用法，这实际上并不值得麻烦。
	 */
	if (fc_context->isReset)
		return true;
	return false;
}

/*
 * AllocSetStats
 *		计算关于 allocset 的内存消耗的统计信息。
 *
 * printfunc：如果不为 NULL，将人类可读的统计字符串传递给此。
 * passthru：将此指针传递给 printfunc。
 * totals：如果不为 NULL，将关于此上下文的统计信息添加到 *totals。
 * print_to_stderr：如果为真，则将统计信息打印到 stderr，否则打印到 elog。
 */
static void fc_AllocSetStats(MemoryContext fc_context,
			  MemoryStatsPrintFunc fc_printfunc, void *fc_passthru,
			  MemoryContextCounters *fc_totals, bool fc_print_to_stderr)
{
	AllocSet	fc_set = (AllocSet) fc_context;
	Size		fc_nblocks = 0;
	Size		fc_freechunks = 0;
	Size		fc_totalspace;
	Size		fc_freespace = 0;
	AllocBlock	fc_block;
	int			fc_fidx;

	/* 将上下文头包含在 totalspace 中 */
	fc_totalspace = MAXALIGN(sizeof(AllocSetContext));

	for (fc_block = fc_set->blocks; fc_block != NULL; fc_block = fc_block->next)
	{
		fc_nblocks++;
		fc_totalspace += fc_block->endptr - ((char *) fc_block);
		fc_freespace += fc_block->endptr - fc_block->freeptr;
	}
	for (fc_fidx = 0; fc_fidx < ALLOCSET_NUM_FREELISTS; fc_fidx++)
	{
		AllocChunk	fc_chunk;

		for (fc_chunk = fc_set->freelist[fc_fidx]; fc_chunk != NULL;
			 fc_chunk = (AllocChunk) fc_chunk->aset)
		{
			fc_freechunks++;
			fc_freespace += fc_chunk->size + ALLOC_CHUNKHDRSZ;
		}
	}

	if (fc_printfunc)
	{
		char		fc_stats_string[200];

		snprintf(fc_stats_string, sizeof(fc_stats_string),
				 "%zu total in %zu blocks; %zu free (%zu chunks); %zu used",
				 fc_totalspace, fc_nblocks, fc_freespace, fc_freechunks,
				 fc_totalspace - fc_freespace);
		fc_printfunc(fc_context, fc_passthru, fc_stats_string, fc_print_to_stderr);
	}

	if (fc_totals)
	{
		fc_totals->nblocks += fc_nblocks;
		fc_totals->freechunks += fc_freechunks;
		fc_totals->totalspace += fc_totalspace;
		fc_totals->freespace += fc_freespace;
	}
}


#ifdef MEMORY_CONTEXT_CHECKING

/*
 * AllocSetCheck
 *		遍历块并检查内存的一致性。
 *
 * 注意：将错误报告为 WARNING，而不是 ERROR 或 FATAL。
 * 否则，当发生问题时，您会发现自己陷入无限循环，
 * 因为当 elog 清理尝试释放内存时，该例程将再次被调用！
 */
static void fc_AllocSetCheck(MemoryContext fc_context)
{
	AllocSet	fc_set = (AllocSet) fc_context;
	const char *fc_name = fc_set->header.name;
	AllocBlock	fc_prevblock;
	AllocBlock	fc_block;
	Size		fc_total_allocated = 0;

	for (fc_prevblock = NULL, fc_block = fc_set->blocks;
		 fc_block != NULL;
		 fc_prevblock = fc_block, fc_block = fc_block->next)
	{
		char	   *fc_bpoz = ((char *) fc_block) + ALLOC_BLOCKHDRSZ;
		long		fc_blk_used = fc_block->freeptr - fc_bpoz;
		long		fc_blk_data = 0;
		long		fc_nchunks = 0;

		if (fc_set->keeper == fc_block)
			fc_total_allocated += fc_block->endptr - ((char *) fc_set);
		else
			fc_total_allocated += fc_block->endptr - ((char *) fc_block);

		/*
		 * 空块 - 空块只能是保留块
		 */
		if (!fc_blk_used)
		{
			if (fc_set->keeper != fc_block)
				elog(WARNING, "problem in alloc set %s: empty block %p",
					 fc_name, fc_block);
		}

		/*
		 * 检查块头字段
		 */
		if (fc_block->aset != fc_set ||
			fc_block->prev != fc_prevblock ||
			fc_block->freeptr < fc_bpoz ||
			fc_block->freeptr > fc_block->endptr)
			elog(WARNING, "problem in alloc set %s: corrupt header in block %p",
				 fc_name, fc_block);

		/*
		 * 块遍历器
		 */
		while (fc_bpoz < fc_block->freeptr)
		{
			AllocChunk	fc_chunk = (AllocChunk) fc_bpoz;
			Size		fc_chsize,
						fc_dsize;

			/* 允许访问块头的私有部分。 */
			VALGRIND_MAKE_MEM_DEFINED(fc_chunk, ALLOCCHUNK_PRIVATE_LEN);

			fc_chsize = fc_chunk->size;	/* 对齐块大小 */
			fc_dsize = fc_chunk->requested_size;	/* 实际数据 */

			/*
			 * 检查块大小
			 */
			if (fc_dsize > fc_chsize)
				elog(WARNING, "problem in alloc set %s: req size > alloc size for chunk %p in block %p",
					 fc_name, fc_chunk, fc_block);
			if (fc_chsize < (1 << ALLOC_MINBITS))
				elog(WARNING, "problem in alloc set %s: bad size %zu for chunk %p in block %p",
					 fc_name, fc_chsize, fc_chunk, fc_block);

			/* 单块块？ */
			if (fc_chsize > fc_set->allocChunkLimit &&
				fc_chsize + ALLOC_CHUNKHDRSZ != fc_blk_used)
				elog(WARNING, "problem in alloc set %s: bad single-chunk %p in block %p",
					 fc_name, fc_chunk, fc_block);

			/*
			 * 如果块已分配，检查 aset 指针是否正确。（如果块
			 * 是自由的，则 aset 是自由列表指针，我们无法
			 * 进行如此简单的检查。）请注意，这是一个
			 * 不完整的测试，因为 palloc(0) 会生成一个
			 * 请求大小为 0 的已分配块。
			 */
			if (fc_dsize > 0 && fc_chunk->aset != (void *) fc_set)
				elog(WARNING, "problem in alloc set %s: bogus aset link in block %p, chunk %p",
					 fc_name, fc_block, fc_chunk);

			/*
			 * 检查在已分配块中填充空间是否被覆盖。
			 */
			if (fc_chunk->aset == (void *) fc_set && fc_dsize < fc_chsize &&
				!sentinel_ok(fc_chunk, ALLOC_CHUNKHDRSZ + fc_dsize))
				elog(WARNING, "problem in alloc set %s: detected write past chunk end in block %p, chunk %p",
					 fc_name, fc_block, fc_chunk);

			/*
			 * 如果块已分配，禁止外部访问块头的私有部分。
			 */
			if (fc_chunk->aset == (void *) fc_set)
				VALGRIND_MAKE_MEM_NOACCESS(fc_chunk, ALLOCCHUNK_PRIVATE_LEN);

			fc_blk_data += fc_chsize;
			fc_nchunks++;

			fc_bpoz += ALLOC_CHUNKHDRSZ + fc_chsize;
		}

		if ((fc_blk_data + (fc_nchunks * ALLOC_CHUNKHDRSZ)) != fc_blk_used)
			elog(WARNING, "problem in alloc set %s: found inconsistent memory block %p",
				 fc_name, fc_block);
	}

	Assert(fc_total_allocated == fc_context->mem_allocated);
}

#endif							/* MEMORY_CONTEXT_CHECKING */
